spb/wp-spboucher-ai
Public
1import type { Metadata } from "next";2import Link from "next/link";3import { getLocale } from "@/lib/i18n/server";4import { fmtDate, href, t } from "@/lib/i18n";5import { timeline } from "@/lib/papers/queries";6export const revalidate = 300;7export async function generateMetadata(): Promise<Metadata> { const d = t(await getLocale()); return { title: d.timeline.title, description: d.timeline.lead, alternates: { canonical: "/timeline" } }; }8export default async function TimelinePage() {9 const locale = await getLocale(); const d = t(locale); const rows = await timeline();10 const byMonth = new Map<string, typeof rows>(); for (const r of rows) { const k = (r.vdate ?? r.released ?? "unknown").slice(0, 7); (byMonth.get(k) ?? byMonth.set(k, []).get(k)!).push(r); }11 const months = [...byMonth.keys()].sort().reverse(); const max = Math.max(1, ...months.map((m) => byMonth.get(m)!.length));12 return (<div className="container py-10"><h1 className="text-3xl sm:text-4xl">{d.timeline.title}</h1><p className="meta mt-2">{d.timeline.lead}</p>13 <svg viewBox={`0 0 ${Math.max(400, months.length * 64)} 160`} role="img" aria-label={d.timeline.title} className="w-full max-w-4xl mt-8 card p-2 bg-bg-2">14 {[...months].reverse().map((m, i) => { const n = byMonth.get(m)!.length; const h = (n / max) * 100; const rel = byMonth.get(m)!.filter((r) => r.version_no === 1).length; return <g key={m} transform={`translate(${i * 64 + 12},0)`}><rect x={8} y={120 - h} width={40} height={h} fill="var(--navy)" opacity={0.85} /><rect x={8} y={120 - (rel / max) * 100} width={40} height={(rel / max) * 100} fill="var(--burgundy)" opacity={0.6} /><text x={28} y={135} textAnchor="middle" fontSize={10} fill="var(--fg-3)" fontFamily="var(--font-mono)">{m}</text><text x={28} y={112 - h} textAnchor="middle" fontSize={11} fill="var(--fg)">{n}</text></g>; })}15 </svg>16 <p className="meta mt-2"><span className="inline-block w-3 h-3 bg-navy align-middle mr-1" /> {d.timeline.releases} + {d.timeline.revisions} · <span className="inline-block w-3 h-3 bg-burgundy align-middle mr-1" /> {d.timeline.releases}</p>17 <ol className="mt-10 relative border-l border-line-2 pl-6 space-y-8">{months.map((m) => <li key={m}><h2 className="text-xl -ml-6 pl-6 relative before:absolute before:left-[-5px] before:top-2.5 before:w-2.5 before:h-2.5 before:rounded-full before:bg-navy">{fmtDate(locale, m + "-01", "month")}</h2><ul className="mt-3 space-y-2">{byMonth.get(m)!.map((r) => <li key={r.slug + r.version_no} className="flex gap-3 text-sm"><span className="font-mono text-navy w-20 shrink-0">{r.identifier}</span><span className="chip shrink-0">{r.version_no === 1 ? d.home.newRelease : d.home.revision} · {r.label}</span><Link href={href(locale, `/papers/${r.slug}`)} className="text-fg no-underline hover:underline">{r.title}</Link></li>)}</ul></li>)}</ol></div>);18}19